# = StarFrame::Sprite -- スプライト

require "starframe/eventable"
require "starframe/sprite/animatable"
require "starframe/sprite/collection"
require "starframe/sprite/collidable"

module StarFrame
  # = Spriteクラス
  # スプライトクラス。
  # 
  # 挙動や当たり判定のモジュールを別途includeして機能を拡張することができる。
  # 
  # == 定義
  # 定義時に、クラス内にインスタンス変数を指定することで画像と中心座標の初期化を行う。
  #   class ExampleSprite < Sprite
  #     @texture = StarRuby::Texture.load("example")
  #     @center_x = 16
  #     @center_y = 16
  #   end
  # 
  # == イベント
  # 以下のイベントが定義されている。
  # === init(options)
  #   初期化時にコールされる。
  #   
  #   移動量の初期値などをここで設定する。
  #   
  #   optionsはnewで渡されたオプション。
  #   
  # === update
  #   更新時にコールされる。
  #   
  #   入力に反応する処理などはこのイベント内に記述する。
  class Sprite
    include StarFrame::Eventable
    
    OPTIONS = {
      :angle => 0,
      :alpha => 0xFF,
      :tone_red => 0,
      :tone_green => 0,
      :tone_blue => 0,
      :src_x => 0,
      :src_y => 0,
      :src_width => "@src_width_default",
      :src_height => "@src_height_default",
      :scale_x => 1,
      :scale_y => 1,
      :saturation => 0xFF,
      :blend_type => ":alpha",
    }
    
    class << self
      # 画像
      attr_reader :texture
      # 描画時の幾何変換の中心のX座標
      attr_reader :center_x
      # 描画時の幾何変換の中心のY座標
      attr_reader :center_y
      
      def inherited klass #:nodoc:
        klass.texture  = @texture
        klass.center_x = @center_x || 0
        klass.center_y = @center_y || 0
      end
      
      def options_reader key, default #:nodoc:
        module_eval <<-END
          def #{key}= value
            if value == #{default}
              @options.delete(:#{key})
            else
              @options[:#{key}] = value
            end
            @#{key} = value
          end
        END
      end
      
      # 描画時の幾何変換の中心座標を配列で取得。
      def center
        [center_x, center_y]
      end
      
      protected
      attr_writer :texture, :center_x, :center_y
    end
    
    # 中心座標 - 描画時の幾何変換の中心のX座標。サブクラス定義内で設定する。
    attr_reader :center_x
    # 中心座標 - 描画時の幾何変換の中心のY座標。サブクラス定義内で設定する。
    attr_reader :center_y
    # 描画時の幾何変換の中心座標を配列で取得。
    def center
      [@center_x, @center_y]
    end
    
    # 位置 - スプライトのX座標。
    attr_accessor :x
    # 位置 - スプライトのY座標。
    attr_accessor :y
    # スプライトの位置を[x, y]の配列で取得。
    def position
      [@x, @y]
    end
    
    # 移動量 - 1フレームあたりの移動量のX座標。
    attr_accessor :movement_x
    # 移動量 - 1フレームあたりの移動量のY座標。
    attr_accessor :movement_y
    # 1フレームあたりの移動量を[x, y]の配列で取得。
    def movement
      [@movement_x, @movement_y]
    end
    
    # 角度 - 描画時の回転角。ラジアンで指定する。
    attr_accessor :angle
    # 角移動量 - 1フレーム当たりの角移動量。ラジアンで指定する。
    attr_accessor :angular_velocity
    
    # 透明度 - 描画時の不透明度。0～255の整数で指定する。0で完全に透明になる。
    attr_accessor :alpha
    # ブレンドの種類 - 
    attr_accessor :blend_type
    # 色調変換の成分 - -255～255の整数で指定する。
    attr_accessor :tone_red, :tone_green, :tone_blue
    # 色調変換の成分をまとめて指定する。
    def tone= tone
      if tone == OPTIONS[:tone_red]
        @options.delete(:tone_red)
        @options.delete(:tone_green)
        @options.delete(:tone_blue)
      else
        @options[:tone_red] = @options[:tone_green] = @options[:tone_blue] = tone
      end
      @tone_red = @tone_green = @tone_blue = tone
    end
    # 彩度 - 0～255の整数で指定する。0で完全にモノトーンになる。
    attr_accessor :saturation
    
    # 描画元座標 - 描画元矩形のX座標。
    attr_accessor :src_x
    # 描画元座標 - 描画元矩形のY座標。
    attr_accessor :src_y
    # 描画元サイズ - 描画元矩形の横幅。
    attr_accessor :src_width
    # 描画元サイズ - 描画元矩形の縦幅。
    attr_accessor :src_height
    
    # 拡大率 - X軸方向の拡大率。
    attr_accessor :scale_x
    # 拡大率 - Y軸方向の拡大率。
    attr_accessor :scale_y
    # 拡大率をまとめて指定する。
    def scale= scale
      if scale == OPTIONS[:scale_x]
        @options.delete(:scale_x)
        @options.delete(:scale_y)
      else
        @options[:scale_x] = @options[:scale_y] = scale
      end
      @scale_x = @scale_y = scale
    end
    
    # 指定した幅に合うように、X軸の拡大率を調整する。
    def scale_width_to width
      self.scale_x = width.quo(@texture.width)
    end
    # 指定した高さに合うように、Y軸の拡大率を調整する。
    def scale_height_to height
      self.scale_y = height.quo(@texture.height)
    end
    # 指定したサイズに縦横の長いほうが収まるように、拡大率を調整する。
    def scale_to_fit_on width, height = width
      self.scale = [width.quo(@texture.width), height.quo(@texture.height)].min
    end
    # 指定したサイズに縦横の短いほうが収まるように、拡大率を調整する。
    def scale_to_crop_on width, height = width
      self.scale = [width.quo(@texture.width), height.quo(@texture.height)].max
    end
    
    OPTIONS.each do |key, default|
      self.options_reader key, default
    end
    
    # 初期化し、initイベントをコールする。
    def initialize x = 0, y = 0, options = {}
      set_texture self.class.texture, self.class.center_x, self.class.center_y
      
      @x, @y = x, y
      @movement_x, @movement_y = 0, 0
      @angular_velocity = 0
      
      @options = {}
      @options[:center_x] = @center_x unless @center_x.zero?
      @options[:center_y] = @center_y unless @center_y.zero?
      
      OPTIONS.each do |key, default_src|
        default = eval("#{default_src}")
        if !options[key] || options[key] == default
          instance_variable_set "@#{key}", default
        else
          instance_variable_set "@#{key}", options[key]
          @options[key] = options[key]
        end
      end
      
      call_init options
    end
    
    # Textureを取得する。
    def to_texture
      @texture
    end
    
    # 自身を他のTextureに描画する。
    def render_to other
      # initializeにて最適化
      other.render_texture to_texture, @x-@center_x, @y-@center_y, @options
    end
    
    # スプライトを移動させる。
    # 
    # updateイベントの直後に自動的に呼ばれるため、通常は実行する必要はない。
    def move
      @x += @movement_x
      @y += @movement_y
      self.angle += @angular_velocity
    end
    # 指定したフレーム分だけスプライトを移動させる。
    def move_minute frame = 1
      @x += @movement_x*frame
      @y += @movement_y*frame
      self.angle = future_angle frame
    end
    # 指定したフレーム分だけ進んだ場合の座標を求める。
    def future_position frame = 1
      [future_x(frame), future_y(frame)]
    end
    # 指定したフレーム分だけ進んだ場合のX座標を求める。
    def future_x frame = 1
      @x + @movement_x*frame
    end
    # 指定したフレーム分だけ進んだ場合のY座標を求める。
    def future_y frame = 1
      @y + @movement_y*frame
    end
    # 指定したフレーム分だけ進んだ場合の角度を求める。
    def future_angle frame = 1
      @angle + @angular_velocity*frame
    end
    
    # 他のスプライトとの距離を求める
    def distance other
      Math.sqrt(squared_distance other)
    end
    # 他のスプライトとの直交距離（平方根を取る前の距離）を求める。
    def squared_distance other
      (@x-other.x)**2 + (@y-other.y)**2
    end
    
    def call_update#:nodoc:
      move
      update
    end
    
    private
    def set_texture texture, center_x, center_y
      @texture, @center_x, @center_y = texture, center_x, center_y
      
      if @texture
        @src_width_default  = @texture.width
        @src_height_default = @texture.height
      else
        @src_width_default  = 0
        @src_height_default = 0
      end
      
      instance_eval <<-END
        def render_to other
          other.render_texture to_texture, @x#{"+%d"%(-@center_x) unless @center_x.zero?}, @y#{"+%d"%(-@center_y) unless @center_y.zero?}, @options
        end
      END
    end
  end
end
